// License:

// Product:
// Author: Robert N. Tenney
// Copyright: April 2009

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation version 2
// of the License.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

//  This license covers all source code, binaries, and other materials related to this project
//  unless specifically stated otherwise.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

using TenneySoftware.Engine;
using TenneySoftware.Engine.PathFinder;
using TenneySoftware.GraphicsManagers;
using TenneySoftware.LevelGenerator;

namespace TenneySoftware.GraphicsManagers {
    /// <summary>
    /// A graphics manager that draws the traditional ASCII characters for tiles using Window's GDI+ drawing functions.
    /// </summary>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1707:IdentifiersShouldNotContainUnderscores")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "GDI")]
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ASCII")]
    public class ASCII_GDI : IGraphicsManager, IDisposable {
        private string pFont = "Consolas";
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists")]
        private List<ObjectImage> TileList;
        private Point place;
        private Point mouseRelLocation;
        private Point mouseOffset;
        private Point pTileLocationFromScreenCoordinates;
        private bool mapDisplayed;
        private string name, description, screenType;
        private bool pCenterPlayer = true;
        private bool pDoFOV = true;
        private bool toggledFOV;
        private bool pGenerated;
        private bool initialized;
        private Point pLevelOffset = Point.Empty;
        private Point pMapOffset = Point.Empty;
        private Bitmap pLevelImage;
        private Bitmap pMapImage;
        private Graphics levelGraphics;
        private Graphics mapGraphics;
        private Bitmap levelDisplay;
        private Graphics levelDisplayGraphics;
        private Bitmap mapDisplay;
        private Graphics mapDisplayGraphics;
        private Size pLevelDisplaySize;
        private Size pMapDisplaySize;
        private bool updatedLevel, updatedMap;
        private Size pTileSizeLevel = new Size(16, 16);
        private Size pTileSizeMap = new Size(8, 8);
        private bool changed;
        
        private RPGEngine pEngine;
        private GraphicsDevice device;
        private BufferedGraphics buffer;
        
        private double ptlu = Stopwatch.GetTimestamp();
        private int callCount;
        private int currentFrameRate;
        private string message;

        private Bitmap shadeLevel, shadeMap, hiLiteLevel, hiLiteMap, levelImageLoc, mapImageLoc;
        private Graphics shadeLevelGr, shadeMapGr, hiLiteLevelGr, hiLiteMapGr, levelGr, mapGr;
        private Brush shade, hiLite, defaultBack, background, forground, mapColor;
        
        private int pFrameRate = 30;
        
        private TileData currentTile;


        /// <summary>
        /// Structure for storing data for a tile's type/state along with the associated
        /// map image and level image.
        /// </summary>
        public struct ObjectImage {
            public RPGObject Object;
            public Bitmap LevelImage;
            public Bitmap MapImage;
            public Bitmap LevelImageShadow;
            public Bitmap MapImageShadow;
            public Bitmap LevelImageHiLite;
            public Bitmap MapImageHiLite;
        }

        /// <summary>
        /// Constructor for the ASCII / GDI graphics manager.
        /// </summary>
        public ASCII_GDI() {
            TileList = new List<ObjectImage>();
            //            this.engine = engine;
            name = "ASCII / GDI+";
            description = "Graphics Manager that uses traditional ASCII symbols. Uses GDI+ to draw tiles.";
            screenType = "GDI+";
        }
        
        /// <summary>
        /// Gets the frame rate.
        /// </summary>
        public int FrameRate {
            get { return currentFrameRate; }
        }
        
        public string Message{
            get{
                return this.message;
            }
            set{
                this.message = value;
            }
        }
        
        /// <summary>
        /// Gets or sets the RPG engine to interact with.
        /// </summary>
        public RPGEngine Engine {
            get { return pEngine; }
            set { pEngine = value; }
        }

        /// <summary>
        /// Gets or sets the tile size for drawing tiles on the level display.
        /// </summary>
        public Size TileSizeLevel {
            get {
                return pTileSizeLevel;
            }
            set {
                pTileSizeLevel = value;
            }
        }

        /// <summary>
        /// Gets or sets the tile size for drawing tiles on the map display.
        /// </summary>
        public Size TileSizeMap {
            get {
                return pTileSizeMap;
            }
            set {
                pTileSizeMap = value;
            }
        }

        /// <summary>
        /// Set font to use for the ASCII characters. Must be a TrueType Font. Default is: Consolas
        /// </summary>
        public string Font {
            get {
                return pFont;
            }
            set {
                pFont = value;
            }
        }

        /// <summary>
        /// Sets the control to perform drawing functions onto.
        /// </summary>
        /// <param name="control">Window's control.</param>
        public void SetGraphicsDevice(Control control){
            device = new GraphicsDevice(control);
            buffer = device.DeviceBuffer;
            updatedLevel = updatedMap = false;
            changed = true;
            this.pMapDisplaySize = this.pLevelDisplaySize = control.Size;
            if(initialized){
                this.Reset();
            }
            this.Update();
        }
        
        /// <summary>
        /// Checks the frame counter.
        /// </summary>
        private void Framecounter() {
            callCount++;
            if ((Stopwatch.GetTimestamp() - ptlu) >= Stopwatch.Frequency) {
                currentFrameRate = callCount;
                callCount = 0;
                ptlu = Stopwatch.GetTimestamp();
            }
        }
        
        /// <summary>
        /// Generates the Images (Level and Map). This is usually called by the
        /// level generator.
        /// </summary>
        private void GenerateImages() {
            pGenerated = false;
            if (!initialized) {
                this.Initialize();
            }
            if (this.pEngine.Level == null) {
                throw new LevelNotAvailableException("Level data has not been generated, cannot generate images.");
            }
            updatedLevel = false;
            updatedMap = false;
            Point offsetTile = Point.Empty;
            Point offsetMap = Point.Empty;
            //            TileData tile;

            ObjectImage found;
            // Check to see if FOV was just toggled
            if (toggledFOV) {
                // If so, clear the screen and redraw everything.
                levelGraphics.Clear(Color.Black);
                mapGraphics.Clear(Color.Black);
            }
            // Iterate through the whole level.
            for (int y = 0; y < pEngine.Height; y++) {
                for (int x = 0; x < pEngine.Width; x++) {
                    // Get the tile at the current location.
                    currentTile = pEngine.Level[x, y];
                    // Check to see if: We are in FOV, if not, check to see if the tile had an object that was changed since
                    // the last draw, and if so, check to see if FOV was toggled recently....
                    if(!currentTile.ObjectChanged  && !pDoFOV && !toggledFOV){
                        // If all of those are false, then we don't want to redraw the tile this turn.
                        continue;
                    }else{
                        // If any are true, then there might be something to draw here, and since there may be a draw, then
                        // acknowledge the object change, and reset....continue with the other checks now...
                        currentTile.ObjectChanged = false;
                    }
                    // Check to see if two conditions are true:
                    // 1 - Was the tile not seen(therefore should be blank) AND are we in FOV,
                    // 2 - Did the tile not change since the last draw AND there was no toggle with FOV AND there are NO
                    //      changes to the tile since last draw.
                    if ((!currentTile.HasSeen && pDoFOV) || (!currentTile.Changed && !toggledFOV && !changed)) {
                        // If either of those conditions are true, this tile does NOT need to be redrawn.
                        continue;
                    }
                    // If one of those conditions are true, as well as the prior check, then the tile needs to be redrawn.
                    // Find the tile to draw...
                    found = TileList.Find(delegate(ObjectImage p) {return p.Object == currentTile.ObjectType;});
                    // If the tile was marked as changed, then reset it, since it was now updated.
                    currentTile.Changed = false;
                    // Set the location to draw this tile on the buffered image.
                    offsetTile.Offset(x * TileSizeLevel.Width, y * TileSizeLevel.Height);
                    offsetMap.Offset(x * TileSizeMap.Width, y * TileSizeMap.Height);
                    // Draw the tile in the correct image.
                    this.levelGraphics.DrawImageUnscaled(((!currentTile.CanSee && pDoFOV) ? found.LevelImageShadow : found.LevelImage), offsetTile);
                    this.mapGraphics.DrawImageUnscaled(((!currentTile.CanSee && pDoFOV) ? found.MapImageShadow : found.MapImage), offsetMap);
                    // Reset offset point.
                    offsetMap = Point.Empty;
                    offsetTile = Point.Empty;
                }
            }
            // Reset all flags, as this drawing turn is complete.
            changed = false;
            toggledFOV = false;
            pGenerated = true;
        }

        /// <summary>
        /// Generates INI template. This would not be used during normal use,
        /// but is helpful when building and debugging.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "INI")]
        public static void GenerateINI() {
            string filename = System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType.Name.ToString() + ".ini";
            FileStream stream;
            try {
                stream = new FileStream(filename, FileMode.CreateNew);
            } catch (IOException) {
                throw new IOException("Information File already exists.");
            }
            StreamWriter writer = new StreamWriter(stream);
            writer.WriteLine("ASCII_GDI Tile Information");
            writer.WriteLine("Colors can be in Known color names, integers or hexadecimal.");
            writer.WriteLine("Tile Name           Character   Background Color    Forground Color");
            writer.WriteLine("-------------------------------------------------------------------");
            foreach (string obj in RPGObject.GetNames(typeof(RPGObject))) {
                writer.WriteLine(obj + "\tASCII (Char)\tBackground Color\tForground Color");
            }
            writer.Close();
            stream.Close();
        }

        /// <summary>
        /// Called to process the information file associated with this manager.
        /// </summary>
        private void ProcessINI() {
            this.ProcessINI(false);
        }

        /// <summary>
        /// Called to process the information file associated with this manager.
        /// </summary>
        /// <param name="generateSwatch">True to generate a png swatch of the tiles.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "INI")]
        private void ProcessINI(bool generateSwatch) {

            string filename = Path.Combine("GraphicsManagers", System.Reflection.MethodInfo.GetCurrentMethod().DeclaringType.Name.ToString());
            string[] lines;
            RPGObject obj = RPGObject.WALL_STONE;
            char character = 'W';
            Color findBackGround, findForeGround;
            Font asciiFont;
            string ascii;
            bool complete = false;
            int fontSizeStep = Math.Max(TileSizeLevel.Width, TileSizeLevel.Height);
            Size fontSize;
            levelImageLoc = new Bitmap(TileSizeLevel.Width, TileSizeLevel.Height);
            levelGr = Graphics.FromImage(levelImageLoc);
            Rectangle levelBounds = new Rectangle(Point.Empty, TileSizeLevel);
            levelBounds.Inflate(5, 5);
            Rectangle mapBounds = new Rectangle(Point.Empty, TileSizeMap);
            Rectangle characterBounds;
            defaultBack = new SolidBrush(Color.Magenta);
            do {
                asciiFont = new Font(this.pFont, fontSizeStep, GraphicsUnit.Pixel);
                fontSize = levelGr.MeasureString(character.ToString(), asciiFont).ToSize();
                characterBounds = new Rectangle(Point.Empty, fontSize);
                if (levelBounds.Contains(characterBounds)) {
                    complete = true;
                }
                fontSizeStep--;
            } while (!complete);

            try {
                lines = System.IO.File.ReadAllLines(filename + ".ini");
            } catch (IOException) {
                throw new IOException(filename + ".ini" + " was not found.");
            }
            foreach (string line in lines) {
                try {
                    string[] segments = line.Split(new char[] {'\t',' '}, StringSplitOptions.RemoveEmptyEntries);
                    try {
                        obj = (RPGObject)Enum.Parse(typeof(RPGObject), segments[0]);
                    } catch (ArgumentException) {
                        continue;
                    }

                    character = char.Parse(segments[1]);
                    try {
                        findBackGround = Color.FromArgb(int.Parse(segments[2], System.Globalization.CultureInfo.CurrentCulture));
                    } catch (FormatException) {
                        try {
                            findBackGround = Color.FromArgb(Convert.ToInt32(segments[2], 16));
                        } catch (FormatException) {
                            findBackGround = Color.FromName(segments[2]);
                        }
                    }
                    try {
                        findForeGround = Color.FromArgb(int.Parse(segments[3], System.Globalization.CultureInfo.CurrentCulture));
                    } catch (FormatException) {
                        try {
                            findForeGround = Color.FromArgb(Convert.ToInt32(segments[3], 16));
                        } catch (FormatException) {
                            findForeGround = Color.FromName(segments[3]);
                        }
                    }
                } catch {
                    throw new IOException("Information file was not properly formatted.");
                }
                background = new SolidBrush(findBackGround);
                forground = new SolidBrush(findForeGround);

                if (findBackGround.A != 255) {
                    mapColor = forground;
                    background = defaultBack;
                } else {
                    mapColor = background;
                }
                switch (obj) {
                    case RPGObject.FLOOR_STONE:
                        defaultBack = background;
                        break;
                    case RPGObject.PLAYER_STONE:
                    case RPGObject.PLAYER_CAVE:
                        mapColor = Brushes.Blue;
                        break;
                    case RPGObject.DOOR_OPEN:
                        mapColor = background;
                        break;
                    case RPGObject.DOOR_CLOSED:
                        mapColor = forground;
                        break;
                }
                ObjectImage temp = new ObjectImage();
                levelImageLoc = new Bitmap(TileSizeLevel.Width, TileSizeLevel.Height);
                levelGr = Graphics.FromImage(levelImageLoc);
                levelGr.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
                mapImageLoc = new Bitmap(TileSizeMap.Width, TileSizeMap.Height);
                mapGr = Graphics.FromImage(mapImageLoc);
                ascii = character.ToString();
                fontSize = levelGr.MeasureString(ascii, asciiFont).ToSize();
                PointF offsetAscii = new PointF(((float)TileSizeLevel.Width / 2f) - ((float)fontSize.Width / 2f), ((float)TileSizeLevel.Height / 2f) - ((float)fontSize.Height / 2f));
                levelGr.FillRectangle(background, levelBounds);
                levelGr.DrawString(ascii, asciiFont, forground, offsetAscii);
                mapGr.FillRectangle(mapColor, mapBounds);
                temp.LevelImage = levelImageLoc;
                temp.MapImage = mapImageLoc;
                temp.Object = obj;

                shadeLevel = (Bitmap)levelImageLoc.Clone();
                shadeLevelGr = Graphics.FromImage(shadeLevel);
                shade = new SolidBrush(Color.FromArgb(128, Color.Black));
                shadeLevelGr.FillRectangle(shade, new Rectangle(Point.Empty, TileSizeLevel));
                temp.LevelImageShadow = shadeLevel;

                shadeMap = (Bitmap)mapImageLoc.Clone();
                shadeMapGr = Graphics.FromImage(shadeMap);
                shadeMapGr.FillRectangle(shade, new Rectangle(Point.Empty, TileSizeMap));
                temp.MapImageShadow = shadeMap;

                hiLiteLevel = (Bitmap)levelImageLoc.Clone();
                hiLiteLevelGr = Graphics.FromImage(hiLiteLevel);
                hiLite = new SolidBrush(Color.FromArgb(75, Color.White));
                hiLiteLevelGr.FillRectangle(hiLite, new Rectangle(Point.Empty, TileSizeLevel));
                temp.LevelImageHiLite = hiLiteLevel;

                hiLiteMap = (Bitmap)mapImageLoc.Clone();
                hiLiteMapGr = Graphics.FromImage(hiLiteMap);
                hiLiteMapGr.FillRectangle(hiLite, new Rectangle(Point.Empty, TileSizeMap));
                temp.MapImageHiLite = hiLiteMap;

                TileList.Add(temp);

            }
            ObjectImage temp2 = new ObjectImage();
            temp2.Object = RPGObject.UNSEEN;
            levelImageLoc = new Bitmap(TileSizeLevel.Width, TileSizeLevel.Height);
            levelGr = Graphics.FromImage(levelImageLoc);
            background = Brushes.Black;
            levelGr.FillRectangle(background, levelBounds);
            mapImageLoc = new Bitmap(TileSizeMap.Width, TileSizeMap.Height);
            mapGr = Graphics.FromImage(mapImageLoc);
            mapColor = Brushes.Black;
            mapGr.FillRectangle(mapColor, mapBounds);
            temp2.LevelImage = levelImageLoc;
            temp2.MapImage = mapImageLoc;
            temp2.LevelImageHiLite = levelImageLoc;
            temp2.MapImageHiLite = mapImageLoc;
            temp2.LevelImageShadow = levelImageLoc;
            temp2.MapImageShadow = mapImageLoc;
            TileList.Add(temp2);



            if (generateSwatch) {
                Bitmap bitmap = new Bitmap(TileSizeLevel.Width * TileList.Count, (TileSizeLevel.Height * 3) + (TileSizeMap.Height * 3));
                Graphics gr = Graphics.FromImage(bitmap);
                Rectangle area = new Rectangle(Point.Empty, TileSizeLevel);
                Point offset = Point.Empty;
                foreach (ObjectImage objIm in TileList) {
                    gr.DrawImage(objIm.LevelImage, offset);
                    offset.Offset(TileSizeLevel.Width, 0);
                }
                offset = new Point(0, TileSizeLevel.Height);
                foreach (ObjectImage objIm in TileList) {
                    gr.DrawImage(objIm.MapImage, offset);
                    offset.Offset(TileSizeMap.Width, 0);
                }
                offset = new Point(0, TileSizeLevel.Height + TileSizeMap.Height);
                foreach (ObjectImage objIm in TileList) {
                    gr.DrawImage(objIm.LevelImageShadow, offset);
                    offset.Offset(TileSizeLevel.Width, 0);
                }
                offset = new Point(0, (TileSizeLevel.Height * 2) + TileSizeMap.Height);
                foreach (ObjectImage objIm in TileList) {
                    gr.DrawImage(objIm.MapImageShadow, offset);
                    offset.Offset(TileSizeMap.Width, 0);
                }
                offset = new Point(0, (TileSizeLevel.Height * 2) + (TileSizeMap.Height * 2));
                foreach (ObjectImage objIm in TileList) {
                    gr.DrawImage(objIm.LevelImageHiLite, offset);
                    offset.Offset(TileSizeLevel.Width, 0);
                }
                offset = new Point(0, (TileSizeLevel.Height * 3) + (TileSizeMap.Height * 2));
                foreach (ObjectImage objIm in TileList) {
                    gr.DrawImage(objIm.MapImageHiLite, offset);
                    offset.Offset(TileSizeMap.Width, 0);
                }
                bitmap.Save(filename + ".png", System.Drawing.Imaging.ImageFormat.Png);
                bitmap.Dispose();
                gr.Dispose();

            }
            asciiFont.Dispose();

        }

        /// <summary>
        /// Initializes this manager.
        /// </summary>
        protected void Initialize() {
            if(pEngine == null){
                throw new RPGEngineObjectMissingException("Engine object was not set.");
            }
            updatedLevel = updatedMap = false;
            pLevelImage = new Bitmap(this.pEngine.Width * TileSizeLevel.Width, this.pEngine.Height * TileSizeLevel.Height);
            levelGraphics = Graphics.FromImage(pLevelImage);
            pMapImage = new Bitmap(this.pEngine.Width * TileSizeMap.Width, this.pEngine.Height * TileSizeMap.Height);
            mapGraphics = Graphics.FromImage(pMapImage);
            levelDisplay = new Bitmap(pLevelDisplaySize.Width, pLevelDisplaySize.Height);
            levelDisplayGraphics = Graphics.FromImage(levelDisplay);
            mapDisplay = new Bitmap(pMapDisplaySize.Width, pMapDisplaySize.Height);
            mapDisplayGraphics = Graphics.FromImage(mapDisplay);
            
            if (TileList.Count == 0) {
                this.ProcessINI();
            }
            initialized = true;
            //            updatedLevel = updatedMap = false;
            //            changed = true;
        }

        /// <summary>
        /// Gets a flag indicating whether or not the images have been generated.
        /// </summary>
        public bool Generated {
            get {
                return pGenerated;
            }

        }
        
        /// <summary>
        /// Gets or sets the flag to indicate whether or not to use FOV/FOW information during drawing.
        /// </summary>
        public bool DoFOV {
            get {
                return pDoFOV;
            }
            set {
                pDoFOV = value;
                toggledFOV = true;
            }
        }
        
        /// <summary>
        /// Gets or sets the flag to indicate whether or not to keep the images centered on the player.
        /// </summary>
        public bool CenterPlayer {
            get {
                return pCenterPlayer;
            }
            set {
                pCenterPlayer = value;
            }
        }

        /// <summary>
        /// Checks to see if the level view is the currently active display.
        /// </summary>
        public bool LevelDisplayActive {
            get {
                return !mapDisplayed;
            }
        }
        
        /// <summary>
        /// Causes updating of images. This will read any changes made and if there are changes, it will redraw the image. The
        /// screen updating is done only within the given frame rate.
        /// </summary>
        public void Update() {
            //            this.updatedLevel = false;
            //            this.updatedMap = false;
            //            this.changed = true;
            Framecounter();
            if (Engine.Level == null){
                return;
            }
            if(this.mapDisplayed){
                if (this.pMapImage != null) {
                    GenerateImages();
                }
                if (!updatedMap) {
                    if (changed) {
                        this.GenerateImages();
                    }
                    if (pCenterPlayer) {
                        CenterOnPlayer();
                    }
                    updatedMap = true;
                }
            } else{
                if (this.pLevelImage != null) {
                    GenerateImages();
                }
                if (!updatedLevel) {
                    if (changed) {
                        this.GenerateImages();
                    }
                    if (pCenterPlayer) {
                        CenterOnPlayer();
                    }
                    updatedLevel = true;
                }
            }
            //            }
            device.BackgroundColor = Color.Black;
            device.ClearBackground();
            device.SmoothMode(System.Drawing.Drawing2D.SmoothingMode.None);
            buffer.Graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.ClearTypeGridFit;
            string text = this.PluginName + ": " + 
                currentFrameRate.ToString(System.Globalization.CultureInfo.CurrentUICulture) + " fps" + Environment.NewLine + 
                Engine.LevelGenerator.PluginName + " - " +
                Engine.LevelGenerator.SettingsName + Environment.NewLine +
                this.message;
            buffer.Graphics.DrawImageUnscaled(this.mapDisplayed ? pMapImage : pLevelImage, this.mapDisplayed ? new Rectangle(MapOffset, pMapDisplaySize) : new Rectangle(LevelOffset, pLevelDisplaySize));
            buffer.Graphics.DrawImageUnscaled(this.ActiveReticle(), this.TileImageLocation);
            buffer.Graphics.DrawString(text, new Font("Arial", 10), Brushes.Black, new Point(1,1));
            buffer.Graphics.DrawString(text, new Font("Arial", 10), Brushes.Yellow, Point.Empty);
            device.Draw();

        }
        
        /// <summary>
        /// Gets the current tile's level location associated with the screen coordinates set with:
        /// void ScreenCoordinates(Point screenCoordinates).
        /// </summary>
        public Point TileLocationFromScreenCoordinates {
            get {
                return pTileLocationFromScreenCoordinates;
            }
        }

        /// <summary>
        /// Gets the current tile's display location associated with the screen coordinates set with:
        /// void ScreenCoordinates(Point screenCoordinates).
        /// </summary>
        public Point TileImageLocation {
            get {
                return new Point((pTileLocationFromScreenCoordinates.X * (mapDisplayed ? TileSizeMap.Width : TileSizeLevel.Width)) + mouseOffset.X, (pTileLocationFromScreenCoordinates.Y * (mapDisplayed ? TileSizeMap.Width : TileSizeLevel.Height)) + mouseOffset.Y);
            }

        }

        /// <summary>
        /// Checks to see if the manager has been initialized, and is ready for drawing.
        /// </summary>
        public bool Ready {
            get {
                if (this.pEngine != null) {
                    return this.pEngine.Level != null;
                } else {
                    return false;
                }

            }

        }

        /// <summary>
        /// Gets or sets the point to offset the display image.
        /// </summary>
        public Point LevelOffset {
            get {
                return this.pLevelOffset;
            }
            set {
                if (updatedLevel) {
                    updatedLevel = false;
                }
                this.pLevelOffset = value;
            }
        }

        /// <summary>
        /// Gets or sets the point to offset the map image.
        /// </summary>
        public Point MapOffset {
            get {
                return this.pMapOffset;
            }
            set {
                if (updatedMap) {
                    updatedMap = false;
                }
                this.pMapOffset = value;
            }
        }

        /// <summary>
        /// Retrieves the currently active reticle.
        /// </summary>
        /// <returns>A bitmap of the active recticle.</returns>
        private Bitmap ActiveReticle() {
            if (mapDisplayed) {
                return this.DrawMapOverlayReticle();
            } else {
                return this.DrawLevelOverlayReticle();
            }
        }

        /// <summary>
        /// Draws the reticle for the level image.
        /// </summary>
        /// <returns>A bitmap of the level recticle.</returns>
        private Bitmap DrawLevelOverlayReticle() {
            bool valid = true;
            // TODO: May want to fix this for keyboard control as well.
            if (!pEngine.CanTileBeOccupied(TileLocationFromScreenCoordinates) || (!pEngine.GetTile(TileLocationFromScreenCoordinates).HasSeen && this.pDoFOV)) {
                valid = false;
            } else {
                valid = true;
            }
            Bitmap temp = new Bitmap(TileSizeLevel.Width, TileSizeLevel.Height);
            Graphics gr = Graphics.FromImage(temp);
            int segmentW = (int)((double)TileSizeLevel.Width / 3.0);
            int segmentH = (int)((double)TileSizeLevel.Height / 3.0);
            Pen reticleValidPen = new Pen(Color.Chartreuse);
            Pen reticleInvalidPen = new Pen(Color.Red);
            Pen usePen = valid ? reticleValidPen : reticleInvalidPen;
            gr.DrawLine(usePen, 0, 0, segmentW, 0);
            gr.DrawLine(usePen, segmentW * 2, 0, TileSizeLevel.Width, 0);
            gr.DrawLine(usePen, 0, 0, 0, segmentH);
            gr.DrawLine(usePen, 0, segmentH * 2, 0, TileSizeLevel.Height);
            gr.DrawLine(usePen, TileSizeLevel.Width - 1, 0, TileSizeLevel.Width - 1, segmentH);
            gr.DrawLine(usePen, TileSizeLevel.Width - 1, segmentH * 2, TileSizeLevel.Width - 1, TileSizeLevel.Height);
            gr.DrawLine(usePen, 0, TileSizeLevel.Height - 1, segmentW, TileSizeLevel.Height - 1);
            gr.DrawLine(usePen, segmentW * 2, TileSizeLevel.Height - 1, TileSizeLevel.Width, TileSizeLevel.Height - 1);
            return temp;
        }

        /// <summary>
        /// Draws the reticle for the map image.
        /// </summary>
        /// <returns>A bitmap of the map recticle.</returns>
        private Bitmap DrawMapOverlayReticle() {
            bool valid = true;
            if (!pEngine.CanTileBeOccupied(TileLocationFromScreenCoordinates) || (!pEngine.GetTile(TileLocationFromScreenCoordinates).HasSeen && this.pDoFOV)) {
                valid = false;
            } else {
                valid = true;
            }
            Bitmap temp = new Bitmap(TileSizeMap.Width, TileSizeMap.Height);
            Graphics gr = Graphics.FromImage(temp);
            int segmentW = (int)((double)TileSizeMap.Width / 3.0);
            int segmentH = (int)((double)TileSizeMap.Height / 3.0);
            Pen reticleValidPen = new Pen(Color.Chartreuse);
            Pen reticleInvalidPen = new Pen(Color.Red);
            Pen usePen = valid ? reticleValidPen : reticleInvalidPen;
            gr.DrawLine(usePen, 0, 0, segmentW, 0);
            gr.DrawLine(usePen, segmentW * 2, 0, TileSizeMap.Width, 0);
            gr.DrawLine(usePen, 0, 0, 0, segmentH);
            gr.DrawLine(usePen, 0, segmentH * 2, 0, TileSizeMap.Height);
            gr.DrawLine(usePen, TileSizeMap.Width - 1, 0, TileSizeMap.Width - 1, segmentH);
            gr.DrawLine(usePen, TileSizeMap.Width - 1, segmentH * 2, TileSizeMap.Width - 1, TileSizeMap.Height);
            gr.DrawLine(usePen, 0, TileSizeMap.Height - 1, segmentW, TileSizeMap.Height - 1);
            gr.DrawLine(usePen, segmentW * 2, TileSizeMap.Height - 1, TileSizeMap.Width, TileSizeMap.Height - 1);
            return temp;
        }
        
        /// <summary>
        /// Offsets the level and map images by the given point value. This is cumulative.
        /// </summary>
        /// <param name="offset">Point to offset images.</param>
        public void OffsetDisplay(Point offset) {
            place.X += offset.X;
            place.Y += offset.Y;
            this.LevelOffset = place;
            this.MapOffset = place;
        }

        /// <summary>
        /// Sets the coordinates for screen to tile conversion.
        /// </summary>
        /// <param name="screenCoordinates">Screen location for translation.</param>
        public void ScreenCoordinates(Point screenCoordinates) {
            mouseOffset = (mapDisplayed ? MapOffset : LevelOffset);
            this.mouseRelLocation = screenCoordinates;
            this.mouseRelLocation.X -= mouseOffset.X;
            this.mouseRelLocation.Y -= mouseOffset.Y;
            this.pTileLocationFromScreenCoordinates = mouseRelLocation;
            this.pTileLocationFromScreenCoordinates.X /= (mapDisplayed ? TileSizeMap.Width : TileSizeLevel.Width);
            this.pTileLocationFromScreenCoordinates.Y /= (mapDisplayed ? TileSizeMap.Width : TileSizeLevel.Height);
        }

        /// <summary>
        /// Offsets the level and map images to center it on the player.
        /// </summary>
        private void CenterOnPlayer() {
            Point inverse = Point.Empty;
            Point center = Point.Empty;
            center.X = pLevelDisplaySize.Width >> 1;
            center.Y = pLevelDisplaySize.Height >> 1;
            inverse.X = (pEngine.PlayerLocation.X * pTileSizeLevel.Width) * -1;
            inverse.Y = (pEngine.PlayerLocation.Y * pTileSizeLevel.Height) * -1;
            inverse.Offset(center);
            LevelOffset = inverse;
            center.X = pMapDisplaySize.Width >> 1;
            center.Y = pMapDisplaySize.Height >> 1;
            inverse.X = (pEngine.PlayerLocation.X * pTileSizeMap.Width) * -1;
            inverse.Y = (pEngine.PlayerLocation.Y * pTileSizeMap.Height) * -1;
            inverse.Offset(center);
            MapOffset = inverse;
        }

        /// <summary>
        /// Resets the manager.
        /// </summary>
        public void Reset() {
            this.Initialize();
        }

        /// <summary>
        /// Gets the name associated with this manager.
        /// </summary>
        public string PluginName {
            get {
                return this.name;
            }
        }

        /// <summary>
        /// Gets the description associated with this manager.
        /// </summary>
        public string PluginDescription {
            get {
                return this.description;
            }
        }

        /// <summary>
        /// Gets the type of screen this will draw onto.
        /// </summary>
        public string PluginScreenType {
            get {
                return this.screenType;
            }
        }
        
        /// <summary>
        /// Returns a string representation of this object. This will return the plugin name.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return this.PluginName;
        }
        
        /// <summary>
        /// Toggles between level view and map view.
        /// </summary>
        public void ToggleDisplay(){
            if(this.mapDisplayed){
                this.mapDisplayed = false;
            } else{
                this.mapDisplayed = true;
            }
        }
        
        /// <summary>
        /// Disposes the resources allocated on this manager.
        /// </summary>
        public void Dispose(){
            this.Dispose(true);
        }

        /// <summary>
        /// Disposes the resources allocated on this manager.
        /// </summary>
        /// <param name="disposeAll">True to dispose all.</param>
        protected virtual void Dispose(bool disposeAll) {
            if (disposeAll) {
                pLevelImage.Dispose();
                levelGraphics.Dispose();
                pMapImage.Dispose();
                mapGraphics.Dispose();
                levelDisplay.Dispose();
                levelDisplayGraphics.Dispose();
                mapDisplay.Dispose();
                mapDisplayGraphics.Dispose();
                mapImageLoc.Dispose();
                levelImageLoc.Dispose();
                mapGr.Dispose();
                levelGr.Dispose();

                mapColor.Dispose();
                defaultBack.Dispose();
                hiLiteLevel.Dispose();
                hiLiteLevelGr.Dispose();
                hiLiteMap.Dispose();
                hiLiteMapGr.Dispose();
                shadeLevel.Dispose();
                shadeLevelGr.Dispose();
                background.Dispose();
                forground.Dispose();
                shade.Dispose();
                hiLite.Dispose();
            }
        }
    }

}
